package br.odb.libscene;


import br.odb.libsvg.SVGShape;
import br.odb.utils.Utils;
import br.odb.utils.math.Vec2;
import br.odb.utils.math.Vec3;


/**
 * 
 * @author Daniel "Monty" Monteiro
 *
 */
public class Sector extends SceneObject implements Constants {
	
	public class InvalidSlotException extends Exception {

		/**
		 * 
		 */
		private static final long serialVersionUID = -3875108539838989405L;

	}
//	private Vector sons = null;
	private boolean master;
	private int parent = 0;
	
	public boolean isMaster() {
//		return sons != null;
		return master;
	}


	public void setIsMaster(boolean master) {
		this.master = master;
//		if ( master ) {
//			if ( sons != null ) {
//			//	Log.d("bzk3", "sector " + id + ": sons not null. being overwritten" );
//			}
//			sons = new Vector(); 
//		} else {
//			sons = null;
//		}
	}
	
	public void addSon( Sector s ) {
		
//		if ( !isMaster() )
//			setIsMaster( true );
//		
//		sons.addElement( s );
		s.setParent( id );
	}

	public void setParent( int sector) {
		parent = sector;		
	}

	private boolean solid;
	private int id;
	/**
	 * 
	 */
	private float x0;
	/**
	 * 
	 */
	private float y0;
	/**
	 * 
	 */
	private float z0;
	/**
	 * 
	 */
	private float x1;
	/**
	 * 
	 */
	private float y1;
	/**
	 * 
	 */
	private float z1;
	/**
	 * 
	 */
	private Mesh mesh;
	/**
	 * 
	 */
	private int[] link = new int[ 6 ];
	private Color[] color = new Color[ 6 ];
	protected Door[] doors = new Door[ 6 ];
	public long lastFrame;
	
	
	

	
	
	

	/**
	 * 
	 * @param id
	 */
	public void setId( int id ) {
		this.id = id;
	}
	
	/**
	 * 
	 * @return
	 */
	public int getId() {
		return id;
	}
	
	/**
	 * 
	 * @param solid
	 */
	public void setSolid( boolean solid ) {
		this.solid = solid;
	}
	
	
	/**
	 * 
	 * @return
	 */
	public boolean isSolid() {
		return solid;
	}
	
	/**
	 * 
	 */
	public Sector(Mesh mesh) {
		this();
		this.mesh = mesh;
		Vec3 v;
		///calculates the bounding box of the mesh
		for ( int c = 0; c < this.mesh.points.size(); ++c ) {
			v = (Vec3) this.mesh.points.get( c );
			
			if ( x0 >= v.getX() )
				x0 = v.getX();

			if ( y0 >= v.getY() )
				y0 = v.getY();

			if ( z0 >= v.getZ() )
				z0 = v.getZ();

			if ( x1 <= v.getX() )
				x1 = v.getX();

			if ( y1 <= v.getY() )
				y1 = v.getY();

			if ( z1 <= v.getZ() )
				z1 = v.getZ();
		}
	}
	
	
	/**
	 * 
	 * @param another
	 */
	public Sector( Sector another ) {
		this();
		initFrom( another );
	}
	
	public void initFrom( Sector another ) {
		
		x0 = another.x0;
		y0 = another.y0;
		z0 = another.z0;
		x1 = another.x1;
		y1 = another.y1;
		z1 = another.z1;
		solid = another.solid;
		parent = another.parent;
		
		
		
		if ( another.getMesh() != null )
			this.mesh = new Mesh( another.getMesh() );
		
		for ( int c = 0; c < 6; ++c ) {
			
			color[ c ] = new Color( another.color[ c ] );
			
			if ( another.getDoor( c ) != null )
				setDoorAt( c, another.getDoor( c ).getSector() );
			
			try {
				this.link[ c ] = another.getLink( c );
			} catch (InvalidSlotException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		
		Sector[] s = another.getSons();
		
		if ( s != null )
			for ( int c = 0; c < s.length; ++c )
				addSon( s[ c ] );
			
	}
	

	
	public Sector[] getSons() {
		Sector[] toReturn = null;
		
//		if ( sons != null ) {
//
//			toReturn = new Sector[ sons.size() ];
//			sons.copyInto( toReturn );
//		}
		
		return toReturn;
	}


	public Sector() {
		this( 0, 0, 0, 0, 0, 0 );
	}
	
	public Sector( float x0, float x1, float y0, float y1, float z0, float z1 ) {
		this.x0 = x0;
		this.y0 = y0;
		this.z0 = z0;
		this.x1 = x1;
		this.y1 = y1;
		this.z1 = z1;
		id = Integer.MIN_VALUE;
		//this.mesh = new Mesh( );
		solid = false;
		link = new int[ 6 ];
		color = new Color[ 6 ];
		
		for ( int c = 0; c < 6; ++c ) {
			link[ c ] = NO_LINK;
			color[ c ] = new Color();
		}
	}


	/**
	 * 
	 * @return
	 */
	public Mesh getMesh() {
		return mesh;
	}

	
	public String toSVGTopView() {
		return toSVGTopView( false );
	}

	public void set( float x0, float y0, float z0, float x1, float y1, float z1 ) {
		this.x0 = x0;
		this.y0 = y0;
		this.z0 = z0;
		this.x1 = x1;
		this.y1 = y1;
		this.z1 = z1;
	}
	
	
	public String toString() {
		
		String sString = "#sector" + this.id + "\n";
		
		
		if ( isSolid() ) {
			
			sString += "o ";
		} else {
			if ( isMaster() )
				sString += "m ";
			else
				sString += "s ";
		}

		if ( World.snapLevel > 2 ) {
			sString += ( int )getX0();
			sString += " ";
			sString += ( int )getDX();
			sString += " ";
			sString += ( int )getY0();
			sString += " ";
			sString += ( int )getDY();
			sString += " ";
			sString += ( int )getZ0();
			sString += " ";
			sString += ( int )getDZ();			
		} else {
			sString += getX0();
			sString += " ";
			sString += getDX();
			sString += " ";
			sString += getY0();
			sString += " ";
			sString += getDY();
			sString += " ";
			sString += getZ0();
			sString += " ";
			sString += getDZ();			
		}
		
		sString += "\n";
		sString += "n " + parent;
		
		sString += "\n";
		sString += "p";
		
		for ( int c = 0; c < 6; ++c ) {
			sString += " " + link[ c ];
		}
		
		for (int c = 0; c < 6; ++c) {
			
			if ( link[ c ] == Sector.NO_LINK /*|| doors[ c ] != null*/ )
				sString += "\nc " + c + " " + color[c].getHTMLColor();
		}
		
		for (int c = 0; c < 6; ++c) {
			if ( doors[ c ] != null )
				sString += "\nd " + c + " " + doors[ c ].getSector();
		}		

		
		return sString;
	}

	public int getLink( int i ) throws InvalidSlotException {
		
		if ( i < 0 || i > 5 )
			throw new InvalidSlotException();
		
		return link[ i ];
	}
	
	public void setLink( int s, int i ) {
		link[ s ] = i;
	}


	public boolean isDegenerate() {
		
		return	Utils.eqFloat( x0, x1 )
		|| Utils.eqFloat( y0, y1 )
		|| Utils.eqFloat( z0, z1 )
		|| ( x1 - x0 < 0.0f )
		|| ( y1 - y0 < 0.0f )
		|| ( z1 - z0 < 0.0f );
	}

	public boolean equals( Sector another ) {
		
		if ( another == null )
			return false;
		
		boolean isEqual = true;
		
		isEqual = isEqual && coincidant( another );
		
		if ( mesh != null  )
			isEqual = isEqual && mesh.equals( another.getMesh() );
		else
			isEqual = isEqual && ( another.getMesh() == null );
				
		try {
			for ( int c = 0; c < 6; ++c ) {			
				isEqual = isEqual && ( link[ c ] == another.getLink( c ) );
				isEqual = isEqual && color[ c ].equals( another.color[ c ] );
				isEqual = isEqual && solid == ( another.solid );
				isEqual = isEqual && id == ( another.id );
			}
		} catch (InvalidSlotException e) {
			return false;
		}
		
		return isEqual;
	}
	
	public boolean equals( Object another ) {
		if ( another instanceof Sector ) {
			return equals( (Sector ) another );
		} else {
			return false;
		}
	}
	
	
	public int hashCode() {
	
		return toString().hashCode();
	}
	
	public boolean coincidant(Sector sector) {
		
		if ( sector == null )
			return false;
		
		if ( 

		Utils.eqFloat(sector.x0, x0) && Utils.eqFloat(sector.y0, y0)
				&& Utils.eqFloat(sector.x1, x1) && Utils.eqFloat(sector.y1, y1)
				&& Utils.eqFloat(sector.z0, z0) && Utils.eqFloat(sector.z1, z1)
		)
			return true;
		
		return false;
	}
	
	// ------------------------------------------------------------------------------------------------------------
	public void setColor( Color color, int index ) {
		setColor( color, index, false );
	}	
	
	public void setColor( Color newColor, int i, boolean fakeShading ) {
		this.color[ i ] = newColor;
		
		if ( !fakeShading || i == 4) {			
			color[ i ].setR( (short) color[ i ].getR() );
			color[ i ].setG( (short) color[ i ].getG() );
			color[ i ].setB( (short) color[ i ].getB() );		
		} else if (i == 5) {
			color[ i ].setR( (short) ( color[ i ].getR() * 0.25f) );
			color[ i ].setG( (short) ( color[ i ].getG() * 0.25f) );
			color[ i ].setB( (short) ( color[ i ].getB() * 0.25f) );
		} else {
			color[ i ].setR( (short) ( color[ i ].getR() * 0.75f * ( (0.15f ) + ( i / 4.0f ) ) ) );
			color[ i ].setG( (short) ( color[ i ].getG() * 0.75f * ( (0.15f ) + ( i / 4.0f ) ) ) );
			color[ i ].setB( (short) ( color[ i ].getB() * 0.75f * ( (0.15f ) + ( i / 4.0f ) ) ) );
		}
		
	}
	
	public boolean contains( Vec3 vec ) {
		float x = vec.getX();
		float y = vec.getY();
		float z = vec.getZ();
		
		return ( ( getX0() <= x || Utils.eqFloat( getX0(), x) ) && x <= getX1() || Utils.eqFloat( getX1(), x) ) &&
				( ( getY0() <= y || Utils.eqFloat( getY0(), y) ) && y <= getY1() || Utils.eqFloat( getY1(), y) ) &&
				( ( getZ0() <= z  || Utils.eqFloat( getZ0(), z) ) && z <= getZ1() || Utils.eqFloat( getZ1(), z) );
	}
	
	
	public boolean contains(Sector sector) {
		
		if ( sector == null )
			return false;
		
		if ( sector.x0 >= x0 && sector.x1 <= x1 &&
				sector.y0 >= y0 && sector.y1 <= y1 &&
				sector.z0 >= z0 && sector.z1 <= z1				
		)
			return true;
		
		return false;
	}


	public float getDX() {
		return x1 - x0;
	}


	public float getDY() {
		return y1 - y0;
	}


	public float getDZ() {
		return z1 - z0;
	}
	
	// ------------------------------------------------------------------------------------------------------------
	public float getX0() {
		return x0;
	}

	// ------------------------------------------------------------------------------------------------------------
	public float getX1() {
		return x1;
	}

	// ------------------------------------------------------------------------------------------------------------
	public float getY0() {
		return y0;
	}

	// ------------------------------------------------------------------------------------------------------------
	public float getY1() {
		return y1;
	}

	// ------------------------------------------------------------------------------------------------------------
	public float getZ0() {
		return z0;
	}

	// ------------------------------------------------------------------------------------------------------------
	public float getZ1() {
		return z1;
	}
	
	public void setDX( float x ) {
		
		if ( x < 0.0f )
			x = 0.0f;
		
		x1 = x0 + x;
	}

	public void setDY( float y ) {

		if ( y < 0.0f )
			y = 0.0f;
		
		y1 = y0 + y;
	}

	public void setDZ( float z ) {
		
		if ( z < 0.0f )
			z = 0.0f;

		z1 = z0 + z;
	}
	
 
	public void setLinks( int l0, int l1, int l2, int l3, int l4, int l5 ) {
		setLink( 0, l0 );
		setLink( 1, l1 );
		setLink( 2, l2 );
		setLink( 3, l3 );
		setLink( 4, l4 );
		setLink( 5, l5 );
	}


	public Color getColor(int d) {
		return color[ d ];
	}


	public void setX0(float x) {
		this.x0 = x;
	}
	
	public void moveTo( Vec3 target ) {
		float dx = getDX();
		float dy = getDY();
		float dz = getDZ();
		
		x0 = target.getX();
		y0 = target.getY();
		z0 = target.getZ();
		
		x1 = x0 + dx;
		y1 = y0 + dy;
		z1 = z0 + dz;
	}


	public void setY0(float y) {
		y0 = y;
	}


	public void setZ0(float z) {
		z0 = z;
	}


	public void setX1(float x) {
		x1 = x;
	}


	public void setY1(float y) {
		y1 = y;
	}


	public void setZ1(float z) {
		z1 = z;
	}
	
	public Vec3 getCenter() {
		return new Vec3( getX0() + ( getDX() / 2 ), getY0() + ( getDY() / 2 ), getZ0() + ( getDZ() / 2 ) );
	}
	
	public boolean touches( Vec3 v ) {
		float x = v.getX();
		float y = v.getY();
		float z = v.getZ();
		
		boolean xIsIn = Float.isInfinite( x ) || ( !Float.isInfinite( x ) && x0 <= x && x <= x1 );
		boolean yIsIn = Float.isInfinite( y ) || ( !Float.isInfinite( y ) && y0 <= y && y <= y1 );
		boolean zIsIn = Float.isInfinite( z ) || ( !Float.isInfinite( z ) && z0 <= z && z <= z1 );
		
		//System.out.println( "x:" + xIsIn + " y:" + yIsIn + " z:" + zIsIn );
		
		return xIsIn && yIsIn && zIsIn;
	}

	public boolean intersect( Sector s1 ) {
		
		if ( s1 == null )
			return false;

		Vec3[] points = s1.getPoints();
		
		for ( int c = 0; c < points.length; ++c ) {
			
			if ( contains( points[ c ] )  )
				return true;
		}
		
		points = getPoints();

		for ( int c = 0; c < points.length; ++c ) {
			
			if ( s1.contains( points[ c ] )  )
				return true;
		}		
		
		
		return false;
	}

	public Vec3[] getPoints() {
		
		Vec3[] toReturn = new Vec3[ 8 ];
		
		toReturn[ 0 ] = new Vec3( x0, y0, z0 );
		toReturn[ 1 ] = new Vec3( x0, y0, z1 );
		toReturn[ 2 ] = new Vec3( x0, y1, z0 );
		toReturn[ 3 ] = new Vec3( x0, y1, z1 );
		toReturn[ 4 ] = new Vec3( x1, y0, z0 );
		toReturn[ 5 ] = new Vec3( x1, y0, z1 );
		toReturn[ 6 ] = new Vec3( x1, y1, z0 );
		toReturn[ 7 ] = new Vec3( x1, y1, z1 );
		
		return toReturn;
	}
	
	public boolean isOpenAt( int slot ) {
		return ( doors[ slot ] == null ) ? link[ slot ] != this.NO_LINK : doors[ slot ].isOpen();
	}
	
	public Door getDoor(int d) {

		return doors[ d ];
	}
	
	public void removeDoorAt( int slot ) {
		doors[ slot ] = null;
	}
	
	public void onSectorEnteredBy( Actor actor ) {
		
	}
	
	public void setDoorAt( int slot, int sector ) {
		doors[ slot ] = new Door( sector );		
	}

	public void onSectorLeftBy( Actor actor) {
		
	}
	
	public int[] getNeighbours() {		
		return this.link;
	}


	public int getParent() {
		return parent;
	}


	public String toSVGTopView( boolean randomColor ) {	
		
		return toSVGTopViewShape( randomColor ).toString();
	}
	
	
	
	public SVGShape toSVGTopViewShape( boolean randomColor ) {
		SVGShape toReturn = SVGShape.makeRect( "" + id, solid );
		
		float x;
		float y;
		float width;
		float height;
		
	
			
			for ( int c = 0; c < 6; ++c ) {
				
				try {
					
					switch ( c ) {
						case Sector.FACE_N:
							x = getX0();
							y = getZ0();
							width = getDX(); 
							height = 1.0f;
						break;
					
						case Sector.FACE_S:
							x = getX0();
							y = getZ1();
							width = getDX(); 
							height = 1.0f;
						break;

						case Sector.FACE_W:
							x = getX0();
							y = getZ0();	
							height = getDZ(); 
							width = 1.0f;
						break;

						case Sector.FACE_E:
							x = getX1();
							y = getZ0();	
							height = getDZ(); 
							width = 1.0f;
						break;
						
						case Sector.FACE_FLOOR:
							x = getX1() - 5;
							y = getZ1() - 5;	
							height = getDZ(); 
							width = getDX();							
						break;
						
						case Sector.FACE_CEILING:
							x = getX0();
							y = getZ0();	
							height = getDZ(); 
							width = getDX();							
						break;				
						default:
							x = getX0();
							y = getZ0();	
							height = 1.0f; 
							width = 1.0f;						
					}
					
					if ( getLink( c ) == Sector.NO_LINK ) {
//						toReturn += "\n<color face = '" + c + "' color = '" +  getColor( c ).getARGBColor() + "' />" ;
//						toReturn += "\n<rect style = 'fill:#000000;' x = '" + x + "' y = '" + y + "' width = '" + width +"' height = '" + height +"' />" ;
					}
					
					if ( getDoor( c ) != null ) {
//						toReturn += "\n<door face = '" + c + "' link = '" +  getLink( c ) + "' />" ;
//						toReturn += "\n<rect style = 'fill:#FF0000;' x = '" + x + "' y = '" + y + "' width = '" + width +"' height = '" + height +"' />" ;						
					}
					
					toReturn.addPoint( new Vec2( x, y ) );
					toReturn.setWidth( width );
					toReturn.setHeight( height );
//					
//					toReturn += "\n<link face = '" + c + "' sector = '" +  getLink( c ) + "' />" ;
				} catch (InvalidSlotException e) {
					e.printStackTrace();
				}
			}
			
			
		return toReturn;
	}


	public SVGShape toSVGTopViewShape() {
		return toSVGTopViewShape( true );
	}


	public int getVisibleFacesCount() {
		int opaqueFaces = 0;
		
		for ( int c = 0; c < 6; ++c )
			try {
				if ( getLink( c ) == Sector.NO_LINK )
					++opaqueFaces;
			} catch (InvalidSlotException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		
		return opaqueFaces;
	}




	public void removeAllDoors() {
		for ( int c = 0; c < 6; ++c )
			this.doors[ c ] = null;
		
	}


//	public boolean intersect(Line3 line) {
//		
//		GeneralPolygon[] quads = makeQuads();
//		Vec3 projection;
//		GeneralPolygon q;
//		
//		for ( int c = 0; c < quads.length; ++c ) {
//			
//			q = quads[ c ];
//			projection = q.getProjection( line );
//			
//			if ( q.contains( projection ) )
//				return true;
//		}
//		
//		return false;
//	}
//
//
//	private GeneralPolygon[] makeQuads() {
//		
//		GeneralPolygon[] toReturn = new GeneralPolygon[ 6 ];
//		GeneralPolygon face;
//		
//		
//		face = new GeneralPolygon();
//		toReturn[ 0 ] = face;
//		
//		
//		face = new GeneralPolygon();
//		toReturn[ 1 ] = face;
//		
//		
//		face = new GeneralPolygon();
//		toReturn[ 2 ] = face;
//		
//		
//		face = new GeneralPolygon();
//		toReturn[ 3 ] = face;
//		
//		
//		face = new GeneralPolygon();
//		toReturn[ 4 ] = face;
//		
//		
//		face = new GeneralPolygon();
//		toReturn[ 5 ] = face;
//		
//		
//		
//		
//		return toReturn;
//	}

	public int touchesInPoints(Sector sector) {
		
		int toReturn = 0;
		
		Vec3[] points = this.getPoints();
		
		for ( int c = 0 ; c < points.length; ++c ) 
			if ( sector.touches( points[ c ] ) )
				++toReturn;
		
		return toReturn;
	}
	
	public boolean touches(Sector sector) {
		
		Vec3[] points = this.getPoints();
		
		for ( int c = 0 ; c < points.length; ++c ) 
			if ( sector.touches( points[ c ] ) )
				return true;
		
		return false;
	}


	
	
}
